﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;

namespace SharpAnalyzer.Utilities
{
    internal delegate Exception ExceptionFactory(string memberName);

    [DebuggerNonUserCode]
    internal static class Verify
    {
        public const string ArgumentNullExceptionDefaultMessage = "The passed reference is not accepted as a valid argument.";
        public const string ArgumentOutOfRangeExceptionDefaultMessage = "The argument is outside the allowable range of values as defined by the invoked method.";

        public static void NotNull<T>(Expression<Func<T>> memberExpression, string message = null)
        {
            ExceptionFactory factory = (ExceptionFactory)(paramName =>
            {
                if (!string.IsNullOrEmpty(message))
                    return new ArgumentNullException(paramName, message);
                return new ArgumentNullException(paramName, "The passed reference is not accepted as a valid argument.");
            });
            Verify.NotNull<T>(memberExpression, factory);
        }

        public static void NotNull<T>(Expression<Func<T>> memberExpression, ExceptionFactory factory)
        {
            if (memberExpression == null)
                throw new ArgumentNullException("memberExpression", "The given member expression is invalid.");
            if (factory == null)
                throw new ArgumentNullException("factory", "The given exception factory is invalid.");
            if (!object.Equals((object)Verify.CachedExpressionCompiler<T>.Compile(memberExpression)(), (object)null))
                return;
            string memberName = Verify.GetMemberName<T>(memberExpression);
            Verify.BuildAndThrowException(factory, memberName);
        }

        private static void BuildAndThrowException(ExceptionFactory factory, string memberName)
        {
            Exception exception = factory(memberName);
            if (exception == null)
                throw new InvalidOperationException("The given factory returned an invalid exception object.");
            throw exception;
        }

        private static string GetMemberName<T>(this Expression<Func<T>> memberExpression)
        {
            return ((MemberExpression)memberExpression.Body).Member.Name;
        }

        [DebuggerNonUserCode]
        public sealed class CachedExpressionCompiler<T>
        {
            private static readonly IDictionary<MemberInfo, Func<T>> Lookup = new Dictionary<MemberInfo, Func<T>>();
            private static readonly object SyncObject = new object();

            private CachedExpressionCompiler()
            {
            }

            public static Func<T> Compile(Expression<Func<T>> memberExpression)
            {
                if (memberExpression == null)
                    throw new ArgumentNullException("memberExpression");
                if (memberExpression.Body is MemberExpression)
                {
                    MemberExpression memberExpression1 = (MemberExpression)memberExpression.Body;
                    if (memberExpression1.Expression is MemberExpression)
                        return Verify.CachedExpressionCompiler<T>.GetMemberExpressionFunc(memberExpression);
                    ConstantExpression constantExpression;
                    if ((constantExpression = memberExpression1.Expression as ConstantExpression) != null)
                    {
                        object fieldValue = ((FieldInfo)memberExpression1.Member).GetValue(constantExpression.Value);
                        if (fieldValue is T)
                            return (Func<T>)(() => (T)fieldValue);
                    }
                }
                return memberExpression.Compile();
            }

            private static Func<T> GetMemberExpressionFunc(Expression<Func<T>> expression)
            {
                MemberInfo member = Verify.CachedExpressionCompiler<T>.GetMemberExpression((MemberExpression)expression.Body).Member;
                Func<T> func;
                if (!Verify.CachedExpressionCompiler<T>.Lookup.TryGetValue(member, out func))
                {
                    lock (Verify.CachedExpressionCompiler<T>.SyncObject)
                    {
                        func = expression.Compile();
                        Verify.CachedExpressionCompiler<T>.Lookup[member] = func;
                    }
                }
                return func;
            }

            private static MemberExpression GetMemberExpression(MemberExpression expr)
            {
                MemberExpression expr1;
                if ((expr1 = expr.Expression as MemberExpression) != null)
                    return Verify.CachedExpressionCompiler<T>.GetMemberExpression(expr1);
                return expr;
            }
        }
    }
}
